// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/base/port_util.h"

#include <limits>
#include <set>

#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "url/url_constants.h"

namespace net {

namespace {

    // The general list of blocked ports. Will be blocked unless a specific
    // protocol overrides it. (Ex: ftp can use ports 20 and 21)
    const int kRestrictedPorts[] = {
        1, // tcpmux
        7, // echo
        9, // discard
        11, // systat
        13, // daytime
        15, // netstat
        17, // qotd
        19, // chargen
        20, // ftp data
        21, // ftp access
        22, // ssh
        23, // telnet
        25, // smtp
        37, // time
        42, // name
        43, // nicname
        53, // domain
        77, // priv-rjs
        79, // finger
        87, // ttylink
        95, // supdup
        101, // hostriame
        102, // iso-tsap
        103, // gppitnp
        104, // acr-nema
        109, // pop2
        110, // pop3
        111, // sunrpc
        113, // auth
        115, // sftp
        117, // uucp-path
        119, // nntp
        123, // NTP
        135, // loc-srv /epmap
        139, // netbios
        143, // imap2
        179, // BGP
        389, // ldap
        465, // smtp+ssl
        512, // print / exec
        513, // login
        514, // shell
        515, // printer
        526, // tempo
        530, // courier
        531, // chat
        532, // netnews
        540, // uucp
        556, // remotefs
        563, // nntp+ssl
        587, // stmp?
        601, // ??
        636, // ldap+ssl
        993, // ldap+ssl
        995, // pop3+ssl
        2049, // nfs
        3659, // apple-sasl / PasswordServer
        4045, // lockd
        6000, // X11
        6665, // Alternate IRC [Apple addition]
        6666, // Alternate IRC [Apple addition]
        6667, // Standard IRC [Apple addition]
        6668, // Alternate IRC [Apple addition]
        6669, // Alternate IRC [Apple addition]
        0xFFFF, // Used to block all invalid port numbers (see
        // third_party/WebKit/Source/platform/weborigin/KURL.cpp,
        // KURL::port())
    };

    // FTP overrides the following restricted ports.
    const int kAllowedFtpPorts[] = {
        21, // ftp data
        22, // ssh
    };

    base::LazyInstance<std::multiset<int>>::Leaky g_explicitly_allowed_ports = LAZY_INSTANCE_INITIALIZER;

} // namespace

bool IsPortValid(int port)
{
    return port >= 0 && port <= std::numeric_limits<uint16_t>::max();
}

bool IsWellKnownPort(int port)
{
    return port >= 0 && port < 1024;
}

bool IsPortAllowedForScheme(int port, const std::string& url_scheme)
{
    // Reject invalid ports.
    if (!IsPortValid(port))
        return false;

    // Allow explitly allowed ports for any scheme.
    if (g_explicitly_allowed_ports.Get().count(port) > 0)
        return true;

    // FTP requests have an extra set of whitelisted schemes.
    if (base::LowerCaseEqualsASCII(url_scheme, url::kFtpScheme)) {
        for (int allowed_ftp_port : kAllowedFtpPorts) {
            if (allowed_ftp_port == port)
                return true;
        }
    }

    // Finally check against the generic list of restricted ports for all
    // schemes.
    for (int restricted_port : kRestrictedPorts) {
        if (restricted_port == port)
            return false;
    }

    return true;
}

size_t GetCountOfExplicitlyAllowedPorts()
{
    return g_explicitly_allowed_ports.Get().size();
}

// Specifies a comma separated list of port numbers that should be accepted
// despite bans. If the string is invalid no allowed ports are stored.
void SetExplicitlyAllowedPorts(const std::string& allowed_ports)
{
    if (allowed_ports.empty())
        return;

    std::multiset<int> ports;
    size_t last = 0;
    size_t size = allowed_ports.size();
    // The comma delimiter.
    const std::string::value_type kComma = ',';

    // Overflow is still possible for evil user inputs.
    for (size_t i = 0; i <= size; ++i) {
        // The string should be composed of only digits and commas.
        if (i != size && !base::IsAsciiDigit(allowed_ports[i]) && (allowed_ports[i] != kComma))
            return;
        if (i == size || allowed_ports[i] == kComma) {
            if (i > last) {
                int port;
                base::StringToInt(base::StringPiece(allowed_ports.begin() + last,
                                      allowed_ports.begin() + i),
                    &port);
                ports.insert(port);
            }
            last = i + 1;
        }
    }
    g_explicitly_allowed_ports.Get() = ports;
}

ScopedPortException::ScopedPortException(int port)
    : port_(port)
{
    g_explicitly_allowed_ports.Get().insert(port);
}

ScopedPortException::~ScopedPortException()
{
    std::multiset<int>::iterator it = g_explicitly_allowed_ports.Get().find(port_);
    if (it != g_explicitly_allowed_ports.Get().end())
        g_explicitly_allowed_ports.Get().erase(it);
    else
        NOTREACHED();
}

} // namespace net
